package de.tum.in.eist.poll.server;


import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;

import de.tum.in.eist.poll.service.PollService;
import de.tum.in.eist.poll.server.dao.FeedbackDaoImpl;
import de.tum.in.eist.poll.server.dao.FeedbackDaoInterface;
import de.tum.in.eist.poll.server.dao.LectureDaoImpl;
import de.tum.in.eist.poll.server.dao.LectureDaoInterface;
import de.tum.in.eist.poll.server.dao.UserDaoImpl;
import de.tum.in.eist.poll.server.dao.UserDaoInterface;
import de.tum.in.eist.poll.shared.beans.Lecture;
import de.tum.in.eist.poll.shared.beans.Feedback;
import de.tum.in.eist.poll.shared.beans.Lecturer;
import de.tum.in.eist.poll.shared.beans.Notification;
import de.tum.in.eist.poll.shared.beans.Question;
import de.tum.in.eist.poll.shared.beans.QuestionGroup;
import de.tum.in.eist.poll.shared.beans.SingleChoiceQuestion;
import de.tum.in.eist.poll.shared.beans.Student;
import de.tum.in.eist.poll.shared.beans.TextQuestion;
import de.tum.in.eist.poll.shared.beans.User;

public class PollServiceImpl extends RemoteServiceServlet implements PollService {
	/**
	 * serialization version for remote service servlet
	 */
	private static final long serialVersionUID = 1L;

	private FeedbackDaoInterface feedbackStore = new FeedbackDaoImpl();
	private UserDaoInterface userStore = new UserDaoImpl();
	private LectureDaoInterface lectureStore = new LectureDaoImpl();
	private HashMap<String,ArrayList<Notification>> notifications = new HashMap<String,ArrayList<Notification>>();
	private PasswordGenerator passwordGenerator;
	private boolean bePersistent = true;

	// TODO LONG: improve, test, remove (this test code)
	public PollServiceImpl() {
		// TODO: remove this setup
		Lecturer prof = new Lecturer("Prof", "new");
		Student student1 = new Student("Student_0", "new", prof.getUserID());
		Student student2 = new Student("Student_1", "new", prof.getUserID());
		userStore.add(prof);
		userStore.add(student1);
		userStore.add(student2);

		Question[] questions = new Question[] {
				new SingleChoiceQuestion("How satisfied are you with today's lecture? (overall rating)", false, new String[] { "Very unsatisfied", "Unsatisfied", "Neutral", "Satisfied","Very Satisfied" }), 
				new TextQuestion("What did you like most?", false), 
				new TextQuestion("What did you dislike or did not understand at all?", false) };
		QuestionGroup group = new QuestionGroup(questions);
		Lecture lecture1 = new Lecture("001", "Prof", "EIST", group);
		
		questions = new Question[] {new SingleChoiceQuestion("What do you have to know after having drunk 10 litres of heavy beer?", false, new String[] { "Fermat", "Euler", "Euklid"})};
		group = new QuestionGroup(questions);
		Lecture lecture2 = new Lecture("002", "Prof", "DS", group);
		lectureStore.put(lecture1);
		lectureStore.put(lecture2);
		
		passwordGenerator = new PasswordGenerator("server/british-english-filtered.txt");
		resumeFromDisk();
	}
	@Override
	public User login(String name, String password) throws IllegalArgumentException {
		User user = userStore.get(name);
		if (user == null || !user.getPassword().equals(password))
			throw new IllegalArgumentException();
		return user;
	}

	@Override
	public List<Student> newStudents(String lecturerID, String prefix, Integer amount) throws IllegalArgumentException {
		if (amount <= 0)
			throw new IllegalArgumentException("PollServiceImpl.newUsers: Amount is negative");
		if (!userStore.exists(lecturerID))
			throw new IllegalArgumentException("Lecturer " + lecturerID + " does not exist");

		ArrayList<Student> result = new ArrayList<Student>();
		// TODO LONG: This algorithm will terminate for sure but may take some
		// take...
		int numericUpperBound = userStore.numberOfUsers() + 2 * amount;
		for (int i = 0; i < amount; ++i) {
			String userID = "";
			do {
				userID = prefix + (int) (Math.floor(Math.random() * numericUpperBound));
			} while (userStore.exists(userID));
			String password = passwordGenerator.newPassword();
			Student student = new Student(userID, password, lecturerID);
			userStore.add(student);
			result.add(student);
		}
		saveUsersToDisk();
		return result;
	}
	
	@Override
	public String removeUsers(String[] userIDs) {
		for (String userID : userIDs)
			userStore.delete(userID);
		saveUsersToDisk();
		return "";
	}

	@Override
	public List<Student> requestStudents(String lecturerID) {
		return userStore.getStudentsByLecturer(lecturerID);
	}

	@Override
	public String addLecture(Lecture lecture) throws IllegalArgumentException {
		if (lecture == null)
			throw new NullPointerException();
		String lecturerID = lecture.getLecturerID();
		if (!userStore.exists(lecturerID))
			throw new IllegalArgumentException("PollServiceImpl#addLecture: " + "Lecturer " + lecturerID + " does not exist");
		User user = userStore.get(lecturerID);
		if (!(user instanceof Lecturer))
			throw new IllegalArgumentException("PollServiceImpl#addLecture: " + "User with ID " + lecturerID + " is not a lecturer");
		if (lectureStore.exists(lecture.getLectureID()))
			throw new IllegalArgumentException("Lecture " + lecture.getLectureID() + " already exists.");
		lectureStore.put(lecture);
		saveLecturesToDisk();
		return "Successfully added lecture" + lecture.getLectureID();
	}

	@Override
	public List<Lecture> requestLectures(String userID) throws IllegalArgumentException {
		// TODO LONG: bad design
		if (!userStore.exists(userID))
			throw new IllegalArgumentException("PollServiceImpl#requestLectures: User " + userID + " does not exist.");
		User user = userStore.get(userID);
		if (user instanceof Student) {
			Student student = (Student) userStore.get(userID);
			return lectureStore.getByLecturer(student.getLecturerID());
		} else if (user instanceof Lecturer) {
			return lectureStore.getByLecturer(userID);
		}
		return new ArrayList<Lecture>();
	}
	
	@Override
	public String closePoll(String lectureID) {
		if (!lectureStore.exists(lectureID))
			return "lecture " + lectureID +  " does not exist";
		Lecture lecture = lectureStore.get(lectureID);
		lecture.closePoll();
		lectureStore.put(lecture);
		saveLecturesToDisk();
		return "poll successfully closed";
	}

	@Override
	public List<Feedback> requestFeedback(String lectureID) {
		return feedbackStore.getByLecture(lectureID);
	}

	@Override
	public String submitFeedback(Feedback feedback) {
		feedbackStore.add(feedback);
		// TODO LONG : answer something
		saveFeedbacksToDisk();
		return "Feedback has been comitted";
	}

	/**
	 * TODO
	 * 
	 * @param lectureID
	 * @return
	 */
	public Question[] getQuestions(String lectureID) {
		return lectureStore.get(lectureID).getQuestions().getQuestions();
	}

	// TODO SEBASTIAN Improve from pull to push
	@Override
	public boolean sendNotification(String[] userIDs, Notification notification) {
		for (String userID : userIDs) {
			if (userStore.exists(userID)) {
				ArrayList<Notification> entry = notifications.get(userID);
				if (entry == null) {
					entry = new ArrayList<Notification>();
					entry.add(notification);
					notifications.put(userID, entry);
				} else if (!entry.contains(notification)) {
					entry.add(notification);
					notifications.put(userID, entry);
				}
			}
		}
		return true;
	}
	
	@Override
	public boolean sendNotificationToStudents(String lecturerID, Notification notification)
			throws IllegalArgumentException {
		if (!userStore.exists(lecturerID))
			throw new IllegalArgumentException("lecturer " + lecturerID + " does not exist,");
		List<Student> students = userStore.getStudentsByLecturer(lecturerID);
		for (Student student: students) {
			// TODO LONG: abstract notifications
			ArrayList<Notification> entry = notifications.get(student.getUserID());
			if (entry == null)
				entry = new ArrayList<Notification>();
			if (!entry.contains(notification)) {
				entry.add(notification);
				notifications.put(student.getUserID(), entry);
			}
		}
		return true;
	}

	@Override
	public ArrayList<Notification> receiveNotification(String userID) {
		ArrayList<Notification> list = notifications.get(userID);
		if (list == null)
			return new ArrayList<Notification>();
		else {
			notifications.remove(userID);
			return list;
		}
	}

	/* (non-Javadoc)
	 * @see de.tum.in.eist.poll.service.PollService#hasSubmitted(java.lang.String, java.lang.String)
	 */
	@Override
	public boolean hasSubmitted(String userId, String lectureId) {
		return feedbackStore.get(lectureId, userId) != null;
	}

	/**
	 * save feedbacks persistently
	 */
	private void saveFeedbacksToDisk() {
		if (!bePersistent)
			return;
		try {
			FileOutputStream fos = new FileOutputStream("server/feedbacks.db");
			ObjectOutputStream out = new ObjectOutputStream(fos);
			out.writeObject(feedbackStore);
			out.close();
			fos.close();
		} catch (IOException e) {
			System.out.println("Exception in PollServiceImpl#saveFeedbacksToDisk");
			System.out.println(e);
		}
	}
	
	/**
	 * save users persistently
	 */
	private void saveUsersToDisk() {
		if (!bePersistent)
			return;
		try {
			FileOutputStream fos = new FileOutputStream("server/users.db");
			ObjectOutputStream out = new ObjectOutputStream(fos);
			out.writeObject(userStore);
			out.close();
			fos.close();
		} catch (IOException e) {
			System.out.println("Exception in PollServiceImpl#saveUsersToDisk");
			System.out.println(e);
		}
	}
	
	/**
	 * save lectures persistently
	 */
	private void saveLecturesToDisk() {
		if (!bePersistent)
			return;
		try {
			FileOutputStream fos = new FileOutputStream("server/lectures.db");
			ObjectOutputStream out = new ObjectOutputStream(fos);
			out.writeObject(lectureStore);
			out.close();
			fos.close();
		} catch (IOException e) {
			System.out.println("Exception in PollServiceImpl#saveLecturesToDisk");
			System.out.println(e);
		}
	}
	
	/**
	 * load persistent data from disk
	 */
	private void resumeFromDisk() {
		File poke = new File("server/feedbacks.db");
		if (poke.exists()) {
			try {
				FileInputStream fis = new FileInputStream("server/feedbacks.db");
				ObjectInputStream in = new ObjectInputStream(fis);
				feedbackStore = (FeedbackDaoImpl)in.readObject();
				in.close();
				fis.close();
			} catch (Exception e) {
				System.out.println("Exception in PollServiceImpl#resumeFromDisk while loading feedbacks");
				System.out.println(e);
			}
		}
		
		poke = new File("server/users.db");
		if (poke.exists()) {
			try {
				FileInputStream fis = new FileInputStream("server/users.db");
				ObjectInputStream in = new ObjectInputStream(fis);
				userStore = (UserDaoImpl)in.readObject();
				in.close();
				fis.close();
			} catch (Exception e) {
				System.out.println("Exception in PollServiceImpl#resumeFromDisk while loading users");
				System.out.println(e);
			}
		}
		
		poke = new File("server/lectures.db");
		if (poke.exists()) {
			try {
				FileInputStream fis = new FileInputStream("server/lectures.db");
				ObjectInputStream in = new ObjectInputStream(fis);
				lectureStore = (LectureDaoImpl)in.readObject();
				in.close();
				fis.close();
			} catch (Exception e) {
				System.out.println("Exception in PollServiceImpl#resumeFromDisk while loading lectures");
				System.out.println(e);
			}
		}
	}
	
	/**
	 * This is only used for testing purposes.
	 * 
	 * @param lecturer The lecturer to add
	 */
	public void addLecturer(Lecturer lecturer) {
		userStore.add(lecturer);
	}

	
	/**
	 * This constructor is just used for testing purposes. Don't use it.
	 * 
	 * @param b
	 */
	public PollServiceImpl(boolean bePersistent) {
		Lecturer prof = new Lecturer("Prof", "new");
		Student student1 = new Student("Student_0", "new", prof.getUserID());
		Student student2 = new Student("Student_1", "new", prof.getUserID());
		userStore.add(prof);
		userStore.add(student1);
		userStore.add(student2);

		Question[] questions = new Question[] {
				new SingleChoiceQuestion("How satisfied are you with today's lecture? (overall rating)", false, new String[] { "Very unsatisfied", "Unsatisfied", "Neutral", "Satisfied","Very Satisfied" }), 
				new TextQuestion("What did you like most?", false), 
				new TextQuestion("What did you dislike or did not understand at all?", false) };
		QuestionGroup group = new QuestionGroup(questions);
		Lecture lecture1 = new Lecture("001", "Prof", "EIST", group);
		
		questions = new Question[] {new SingleChoiceQuestion("What do you have to know after having drunk 10 litres of heavy beer?", false, new String[] { "Fermat", "Euler", "Euklid"})};
		group = new QuestionGroup(questions);
		Lecture lecture2 = new Lecture("002", "Prof", "DS", group);
		lectureStore.put(lecture1);
		lectureStore.put(lecture2);
		
		passwordGenerator = new PasswordGenerator(new String[] {"sa", "yu", "shi", "ma", "to"});
		this.bePersistent = bePersistent;
	}
}
